#include <EB0/EB0.h>
#include <iostream>
#include <algorithm>

namespace EB0
{
namespace Window
{
	// Static Member
	sf::Image* RenderWindow::mWindowIcon = 0; // null pointer
	int RenderWindow::mWindowCount = 0; // no windows open

#pragma region Constructor / Destructor
	// Constructor
	RenderWindow::RenderWindow(int width, int height, char* title, bool resize) :
		sf::RenderWindow(sf::VideoMode(width, height), title, sf::Style::Close | (resize ? sf::Style::Resize : 0))
	{
		// Preserve OpenGL states
		//PreserveOpenGLStates(true); // Is this necessary? I turned it on while fixing Viewports...

		// Create icon for the window
		if (!mWindowIcon)
			mWindowIcon = Managers::ImageManager::getSingletonPtr()->createImage(WINDOW_ICON_PATH);

		SetIcon(mWindowIcon->GetWidth(), mWindowIcon->GetHeight(), mWindowIcon->GetPixelsPtr());

		// Default clear colour for background
		setClearColor(0, 0, 0);

		// Create StateManager
		mStateManager = new Managers::StateManager(this);

		// Default values
		mDirtyLayerList = false;
		mDirtyWindowListenerSet = false;
		mDirtyFrameListenerSet = false;

		// Increment total number of windows created
		mWindowCount++;
	}

	// Destructor
	RenderWindow::~RenderWindow()
	{
		// Delete StateManager
		if (mStateManager)
		{
			delete mStateManager;
			mStateManager = 0;
		}

		// Destroy layers
		destroyAllLayers();

		// Remove listeners
		removeAllWindowListeners();
		removeAllFrameListeners();

		// Default values
		setClearColor(0, 0, 0);
		mDirtyLayerList = false;
		mDirtyWindowListenerSet = false;
		mDirtyFrameListenerSet = false;

		// Decrement the number of windows
		mWindowCount--;

		// Should the window icon be destroyed?
		if (mWindowCount <= 0 && mWindowIcon)
			Managers::ImageManager::getSingletonPtr()->destroyImage(mWindowIcon);
	}
#pragma endregion

#pragma region Properties
	// Get StateManager
	Managers::StateManager* RenderWindow::getStateManager() const
	{
		return mStateManager;
	}

	// Set clear colour for the background
	void RenderWindow::setClearColor(sf::Color color)
	{
		mClearColor = color;
	}

	// Set clear colour for the background
	void RenderWindow::setClearColor(sf::Uint8 r, sf::Uint8 g, sf::Uint8 b, sf::Uint8 a)
	{
		mClearColor = sf::Color(r, g, b, a);
	}

	// Get clear colour for the background
	sf::Color RenderWindow::getClearColor()
	{
		return mClearColor;
	}
#pragma endregion

#pragma region Event Handling / Input
	// Handle incoming events
	void RenderWindow::handleEvents()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Process event queue
		while (IsOpened() && GetEvent(mEvent) && !mWindowListeners.empty())
		{
			// We are only concerned about the dirty listener set flag while in the event loop
			mDirtyWindowListenerSet = false;

			// Traverse each window listener calling the appropriate event
			static WindowListenerSet::iterator windowListenerIter; // frequently created otherwise
			windowListenerIter = mWindowListeners.begin();

			static WindowListenerSet completedListeners; // set of completed listeners, for additional checking on dirty sets
			static bool dirtyListenerFlag; // we need only check the completedListeners list when the set has been dirtied
			completedListeners.clear(); // clear the array of called listeners
			dirtyListenerFlag = false; // no need to do the extra checking... yet

			while (windowListenerIter != mWindowListeners.end())
			{
				// Has this listener already been called?
				if (dirtyListenerFlag && completedListeners.find(*windowListenerIter) != completedListeners.end())
				{
					// Skip it
					windowListenerIter++;
					continue;
				}

				// Add the listener to the already completed list
				completedListeners.insert(*windowListenerIter);

				// Check all event types
				switch (mEvent.Type)
				{
					// Basic Window Events
				case sf::Event::Closed:
					(*windowListenerIter)->onClose(this);
					break;

				case sf::Event::Resized:
					(*windowListenerIter)->onResize(this, mEvent.Size.Width, mEvent.Size.Height);
					break;

				case sf::Event::GainedFocus:
					(*windowListenerIter)->onGainFocus(this);
					break;

				case sf::Event::LostFocus:
					(*windowListenerIter)->onLoseFocus(this);
					break;

					// Keyboard
				case sf::Event::TextEntered:
					(*windowListenerIter)->onCharEnter(this, mEvent.Text.Unicode);
					break;

				case sf::Event::KeyPressed:
					(*windowListenerIter)->onKeyPress(this, mEvent.Key);
					break;

				case sf::Event::KeyReleased:
					(*windowListenerIter)->onKeyRelease(this, mEvent.Key);
					break;

					// Mouse
				case sf::Event::MouseEntered:
					(*windowListenerIter)->onMouseEnter(this);
					break;

				case sf::Event::MouseLeft:
					(*windowListenerIter)->onMouseLeave(this);
					break;

				case sf::Event::MouseButtonPressed:
					(*windowListenerIter)->onMouseButtonPress(this, mEvent.MouseButton);
					break;

				case sf::Event::MouseButtonReleased:
					(*windowListenerIter)->onMouseButtonRelease(this, mEvent.MouseButton);
					break;

				case sf::Event::MouseMoved:
					(*windowListenerIter)->onMouseMove(this, mEvent.MouseMove);
					break;

				case sf::Event::MouseWheelMoved:
					(*windowListenerIter)->onMouseWheelMove(this, mEvent.MouseWheel.Delta);
					break;

					// No default
				default:
					break;
				}

				// Has the window listeners set been altered?
				if (mDirtyWindowListenerSet)
				{
					// Go to the beginning of the list if there are still window listeners
					if (!mWindowListeners.empty())
						windowListenerIter = mWindowListeners.begin();

					// Reset the flag as the situation is now being taken care of
					mDirtyWindowListenerSet = false;
					dirtyListenerFlag = true; // we'll need to perform additional checking now...
				}
				else
					windowListenerIter++;
			}
		}
	}
#pragma endregion

#pragma region Update State Manager
	// Update State Manager
	void RenderWindow::update(float dt)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Update State Manager
		mStateManager->update(dt);
	}
#pragma endregion

#pragma region Rendering
	// Render contents of the window
	void RenderWindow::render(float dt)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// The window must be open for any rendering to occur
		if (!IsOpened())
			return;

		// Perform onFrameStart() of frame listeners
		mDirtyFrameListenerSet = false; // only necessary during traversal

		// Traverse each frame listener calling the appropriate event
		static FrameListenerSet::iterator frameListenerIter; // frequently created otherwise, for traversing
		static FrameListenerSet completedListeners; // set of completed listeners, for additional checking on dirty sets
		static bool dirtyListenerFlag = false; // we need only check the completedListeners list when the set has been dirtied

		if (!mFrameListeners.empty())
		{
			frameListenerIter = mFrameListeners.begin();
			completedListeners.clear(); // clear the array of called listeners
			dirtyListenerFlag = false; // no need to do the extra checking... yet

			while (frameListenerIter != mFrameListeners.end())
			{
				// Has this listener already been called?
				if (dirtyListenerFlag && completedListeners.find(*frameListenerIter) != completedListeners.end())
				{
					// Skip it
					frameListenerIter++;
					continue;
				}

				// Add the listener to the already completed list
				completedListeners.insert(*frameListenerIter);

				// Call onFrameStart()
				(*frameListenerIter)->onFrameStart(this);

				// Has the window listeners set been altered?
				if (mDirtyFrameListenerSet)
				{
					// Go to the beginning of the list if there are still frame listeners
					if (!mFrameListeners.empty())
						frameListenerIter = mFrameListeners.begin();

					// Reset the flag as the situation is now being taken care of
					mDirtyFrameListenerSet = false;
					dirtyListenerFlag = true; // we'll need to perform additional checking now...
				}
				else
					frameListenerIter++;
			}
		}

		// The window may have been closed through a frame listener
		if (!IsOpened())
			return;

		// Clear the window's contents
		Clear(mClearColor);

		// Render all layers
		mDirtyLayerList = false; // only necessary during traversal

		// Sort list of renderables by the way they will be displayed
		mLayers.sort(Layer::comparePtr);

		// Traverse through each renderable
		static LayerList::iterator layerIter; // frequently created otherwise, for traversing
		static LayerSet completedLayers; // set of completed layers, for additional checking on dirty sets
		static bool dirtyLayerFlag = false; // we need only check the completedLayers list when the set has been dirtied

		if (!mLayers.empty())
		{
			layerIter = mLayers.begin();
			completedLayers.clear(); // clear the array of called layers
			dirtyLayerFlag = false; // no need to do the extra checking... yet

			while (layerIter != mLayers.end())
			{
				// Has this layer already been drawn?
				if (dirtyLayerFlag && completedLayers.find(*layerIter) != completedLayers.end())
				{
					// Skip it
					layerIter++;
					continue;
				}

				// Add the layer to the already completed list
				completedLayers.insert(*layerIter);

				// Draw the layer's contents
				(*layerIter)->render(*this);

				// Has the layer map been altered?
				if (mDirtyLayerList)
				{
					// Go to the beginning of the list if there are still remaining layers
					if (!mLayers.empty())
						layerIter = mLayers.begin();

					// Reset the flag as the situation is now being taken care of
					mDirtyLayerList = false;
					dirtyLayerFlag = true; // we'll need to perform additional checking now..

					// Resort the list, even though this will have no effect on previously drawn objects...
					mLayers.sort(Layer::comparePtr);
				}
				else
					layerIter++;
			}
		}

		// Display the window
		Display();

		// Perform onFrameEnd() of frame listeners
		mDirtyWindowListenerSet = false; // only necessary during traversal

		// Traverse each frame listener calling the appropriate event
		if (!mFrameListeners.empty())
		{
			frameListenerIter = mFrameListeners.begin();
			completedListeners.clear(); // clear the array of called listeners
			dirtyListenerFlag = false; // no need to do the extra checking... yet

			while (frameListenerIter != mFrameListeners.end())
			{
				// Has this listener already been called?
				if (dirtyListenerFlag && completedListeners.find(*frameListenerIter) != completedListeners.end())
				{
					// Skip it
					frameListenerIter++;
					continue;
				}

				// Add the listener to the already completed list
				completedListeners.insert(*frameListenerIter);

				// Call onFrameEnd()
				(*frameListenerIter)->onFrameEnd(this);

				// Has the frame listeners set been altered?
				if (mDirtyFrameListenerSet)
				{
					// Go to the beginning of the list if there are still frame listeners
					if (!mFrameListeners.empty())
						frameListenerIter = mFrameListeners.begin();

					// Reset the flag as the situation is now being taken care of
					mDirtyFrameListenerSet = false;
					dirtyListenerFlag = true; // we'll need to perform additional checking now...
				}
				else
					frameListenerIter++;
			}
		}
	}
#pragma endregion

#pragma region Layer Management
	// Create a layer given a zindex
	Layer* RenderWindow::createLayer(unsigned int zindex)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// A zindex of UINT_MAX indicates that the layer should be added to the top
		if (zindex == UINT_MAX)
			zindex = getMaxLayerZ() + 1; // must be higher than the previous max layer

		// Create a new layer
		sf::Vector2f viewCenter(GetWidth() / 2.0f, GetHeight() / 2.0f);
		sf::Vector2f viewHalfSize(GetWidth() / 2.0f, GetHeight() / 2.0f);
		mLayers.push_back(new Layer(this, viewCenter, viewHalfSize, zindex));

		mDirtyLayerList = true;

		return mLayers.back();
	}

	// Destroy a layer with a given pointer
	bool RenderWindow::destroyLayer(Layer*& layer)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Remove all instances of this layer, assuming only one instance ever exists
		if (!mLayers.empty())
		{
			LayerList::iterator layerIter;
			bool found = false;

			for (layerIter = mLayers.begin(); layerIter != mLayers.end() && !found; layerIter++)
			{
				if (layer == *layerIter)
				{
					delete (*layerIter);
					(*layerIter) = 0;
					mLayers.erase(layerIter);
					layer = 0;

					mDirtyLayerList = true;
					return true;
				}
			}
		}

		return false;
	}

	// Destroy all Layers
	void RenderWindow::destroyAllLayers()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Destroy all layers
		if (!mLayers.empty())
		{
			LayerList::iterator layerIter;

			for (layerIter = mLayers.begin(); layerIter != mLayers.end(); layerIter++)
			{
				delete (*layerIter);
				(*layerIter) = 0;
			}

			mLayers.clear();

			mDirtyLayerList = true;
		}
	}

	// Get number of Layers
	int RenderWindow::getNumLayers() const
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		return (signed)mLayers.size();
	}

	// Get the maximum layer z-index which will be useful for appending a layer to the top
	unsigned short RenderWindow::getMaxLayerZ()
	{
		// Traverse each layer to find the max z-index.
		unsigned short maxz = 0;

		LayerList::iterator layerIter;

		for (layerIter = mLayers.begin(); layerIter != mLayers.end(); layerIter++)
		{
			if ((*layerIter)->z > maxz)
				maxz = (*layerIter)->z;
		}

		return maxz;
	}
#pragma endregion

#pragma region Window Listeners
	// Attach a window listener
	void RenderWindow::attachWindowListener(EB0::Window::WindowListener* windowListener)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Abort on bad pointer
		if (!windowListener) return;

		mWindowListeners.insert(windowListener);
		mDirtyWindowListenerSet = true;
	}

	// Remove a window listener with a given pointer
	void RenderWindow::removeWindowListener(EB0::Window::WindowListener* windowListener)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Abort on bad pointer
		if (!windowListener) return;

		mWindowListeners.erase(windowListener);
		mDirtyWindowListenerSet = true;
	}

	// Remove all window listeners
	void RenderWindow::removeAllWindowListeners()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		mWindowListeners.clear();
	}
#pragma endregion

#pragma region Frame Listeners
	// Attach a frame listener
	void RenderWindow::attachFrameListener(FrameListener* frameListener)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Abort on bad pointer
		if (!frameListener) return;

		mFrameListeners.insert(frameListener);
		mDirtyFrameListenerSet = true;
	}

	// Remove a frame listener with a given pointer
	void RenderWindow::removeFrameListener(FrameListener* frameListener)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Abort on bad pointer
		if (!frameListener) return;

		mFrameListeners.erase(frameListener);
		mDirtyFrameListenerSet = true;
	}

	// Remove all frame listeners
	void RenderWindow::removeAllFrameListeners()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		mFrameListeners.clear();
	}
#pragma endregion
} // namespace Window
}